/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "image_resize.h"
#include "dib.h"
#include <stdlib.h>
#include <stdio.h>
#include "math2.h"


byte_t getBit( byte_t* bytes, int index, int invertBit )
{
    int byteIndex = index >> 3;
    byte_t* base = bytes + byteIndex;
    byte_t baseChar = *base;
    byte_t bitIndex = index & 7;
    byte_t bit = ( baseChar >> ( 7 - bitIndex ) ) & 0x01;
    return invertBit ? !bit : bit;
    //return bit;
}

bool resizeImageBilinear( byte_t** dest, uint_t destWidth, uint_t destHeight, byte_t* source )
{
    //uint_t t = GetTickCount();

    *dest = NULL;
    byte_t* destData = NULL;

    PBITMAPINFOHEADER bih = ( PBITMAPINFOHEADER ) source;

    uint_t stride = Dib::GetStride( bih );
    byte_t* sourceBits = ( byte_t* ) Dib::GetBits( bih );
    uint16_t sourceBitCount = bih->biBitCount;
    uint_t width = bih->biWidth;
    uint_t height = bih->biHeight;
    bool invertBits = bih->biBitCount == 1 && ( ( ( RGBQUAD* ) ( ( byte_t* ) source + sizeof( BITMAPINFOHEADER ) ) )->rgbGreen == 0xFF );

    uint16_t destBitCount = sourceBitCount == 24 ? 24 : 8;
    uint_t destStride = ( ( destWidth * destBitCount + 31 ) & ( ~31 ) ) >> 3;

    uint_t headerLength = sizeof( BITMAPINFOHEADER ) + ( sourceBitCount == 24 ? 0 : ( sizeof( RGBQUAD ) << 8 ) );
    uint_t destLength = headerLength + destStride * destHeight;
    destData = ( byte_t* ) ::malloc( destLength );
    ::memset( destData, 0xFF, destLength );

    // copy dib header
    switch ( sourceBitCount )
    {
    case 1:
        {
            ::memcpy( destData, source, sizeof( BITMAPINFOHEADER ) );

            byte_t* palette = destData + sizeof( BITMAPINFOHEADER );

            for ( int i = 0; i < 256; i++ )
            {
                RGBQUAD rgb = { i, i, i, 0 };
                ::memcpy( palette + sizeof( RGBQUAD ) * i, &rgb, sizeof( RGBQUAD ) );
            }
        }
        break;

    case 8:
    case 24:

        ::memcpy( destData, source, headerLength );

        break;
    }

    // edit the header
    PBITMAPINFOHEADER destBih = ( PBITMAPINFOHEADER ) destData;
    destBih->biWidth = destWidth;
    destBih->biHeight = destHeight;
    destBih->biSizeImage = destStride * destHeight;
    destBih->biBitCount = destBitCount;
    destBih->biClrImportant = destBih->biClrUsed = sourceBitCount == 24 ? 0 : 256;

    double ratio = destWidth / ( double ) width;
    double ratio1 = width / ( double ) destWidth;

    byte_t* line = destData + headerLength;

    double rx, ry;
    double dx, dy;
    int ix, iy;
    int x1, y2, x2, y1;
    double f00, f01, f11, f10;
    double b1, b2, b3, b4;
    byte_t p = 0;
    byte_t channels = destBitCount >> 3;
    byte_t c = 0;

    for ( uint_t j = 0; j < destHeight - 1; j++ )
    {
        ry = j * ratio1;

        for ( uint_t i = 0; i < destWidth; i++ )
        {
            rx = i * ratio1;
            //if ( ry == 0 ) ry = 1;

            dx = rx - ( int ) rx; // 1.0F - ( rx - ( int ) rx );
            dy = ry - ( int ) ry; //1.0F - ( ry - ( int ) ry );//

            ix = ( int ) rx;
            iy = ( int ) ry;

            x1 = ix > ( int ) width ? ( int ) width - 1 : ix;
            y1 = iy > ( int ) height ? ( int ) height - 1 : iy;
            /*x2 = ix >= ( width - 1 ) ? ix : ( ix + 1 );
            y2 = iy >= ( height - 1 ) ? iy : ( iy + 1 );*/
            x2 = x1 >= ( ( int ) width - 1 ) ? x1 : ( x1 + 1 );
            y2 = y1 >= ( ( int ) height - 1 ) ? y1 : ( y1 + 1 );

            c = 0;

            while ( c < channels )
            {
                int y1s = y1 * stride;
                int y2s = y2 * stride;

                if ( bih->biBitCount != 1 )
                {
                    byte_t* sbc = sourceBits + c;
                    int x1c = x1 * channels;
                    int x2c = x2 * channels;

                    f00 = *( sbc + y1s + x1c );
                    f01 = *( sbc + y2s + x1c );
                    f11 = *( sbc + y2s + x2c );
                    f10 = *( sbc + y1s + x2c );

                    /*f00 = *( sourceBits + y1 * stride + x1 * channels + c );
                    f01 = *( sourceBits + y2 * stride + x1 * channels + c );
                    f11 = *( sourceBits + y2 * stride + x2 * channels + c );
                    f10 = *( sourceBits + y1 * stride + x2 * channels + c );*/
                }
                else
                {
                    byte_t* sby1s = sourceBits + y1s;
                    byte_t* sby2s = sourceBits + y2s;

                    f00 = getBit( sby1s, x1, invertBits ) ? 0xFF : 0x00;
                    f01 = getBit( sby2s, x1, invertBits ) ? 0xFF : 0x00;
                    f11 = getBit( sby2s, x2, invertBits ) ? 0xFF : 0x00;
                    f10 = getBit( sby1s, x2, invertBits ) ? 0xFF : 0x00;

                    /*f00 = getBit( sourceBits + y1 * stride, x1, invertBits ) ? 0xFF : 0x00;
                    f01 = getBit( sourceBits + y2 * stride, x1, invertBits ) ? 0xFF : 0x00;
                    f11 = getBit( sourceBits + y2 * stride, x2, invertBits ) ? 0xFF : 0x00;
                    f10 = getBit( sourceBits + y1 * stride, x2, invertBits ) ? 0xFF : 0x00;*/
                }

                b1 = f00;
                b2 = f10 - f00;
                b3 = f01 - f00;
                b4 = f00 - f10 - f01 + f11;

                p = ( byte_t ) ( b1 + b2 * dx + b3 * dy + b4 * dx * dy );

                line[ i * channels + c ] = p;

                c++;
            }
        }

        line += destStride;
    }

    *dest = destData;

    //t = GetTickCount() - t;

    return true;
}

bool resizeImageNearest( BITMAPINFOHEADER** dest, size_t& destSize, long viewLeft, long viewTop, long viewWidth, long viewHeight, double zoomFactor, const Pixels* pixels )
{
    if ( viewLeft == viewWidth || viewTop == viewHeight || viewLeft < 0 || viewTop < 0 ) return false;
    //uint_t t = GetTickCount();

    bool reallocated = false;
    const Pixels& p = *pixels;
    //*dest = NULL;
    byte_t* destData = NULL;

    long width1 = p.width - 1;
    long height1 = p.height - 1;

    uint16_t destBitCount = p.bitsPerPixel == 24 ? 24 : 8;
    uint_t destStride = ( ( viewWidth * destBitCount + 31 ) & ( ~31 ) ) >> 3;
    bool bw = p.bitsPerPixel == 1;
    uint_t headerLength = sizeof( BITMAPINFOHEADER ) + ( p.bitsPerPixel == 24 ? 0 : ( sizeof( RGBQUAD ) << 8 ) );
    uint_t destLength = headerLength + destStride * viewHeight;

    reallocated = destSize <= destLength;
    if ( reallocated )
    {
        ::free( *dest );
        destData = ( byte_t* ) ::malloc( destLength );
        destSize = destLength;
    }
    else
    {
        destData = ( byte_t* ) *dest;
    }
    //destData = ( byte_t* ) ::malloc( destLength ) ;

    // copy dib header
    if ( p.bitsPerPixel != 24 )
    {
        byte_t* palette = destData + sizeof( BITMAPINFOHEADER );
        byte_t* pp = palette;

        for ( int i = 0; i < 256; i++ )
        {
            RGBQUAD rgb = { i, i, i, 0 };
            ::memcpy( pp, &rgb, sizeof( RGBQUAD ) );
            pp += sizeof( RGBQUAD );
        }
    }

    // edit the header
    PBITMAPINFOHEADER destBih = ( PBITMAPINFOHEADER ) destData;
    destBih->biWidth = viewWidth;
    destBih->biHeight = -viewHeight;
    destBih->biSizeImage = destStride * viewHeight;
    destBih->biBitCount = destBitCount;
    destBih->biClrImportant = destBih->biClrUsed = p.bitsPerPixel == 24 ? 0 : 256;
    destBih->biYPelsPerMeter = destBih->biXPelsPerMeter = ::lround( p.resolution / 0.254F );
    destBih->biClrUsed = 0;
    destBih->biPlanes = 1;
    destBih->biSize = sizeof( BITMAPINFOHEADER );
    destBih->biCompression = BI_RGB;

    double ratio1 = 1 / zoomFactor;

    byte_t* line = destData + headerLength;

    double rx, ry;
    int ix, iy;
    int x1, y1;
    double b1 = 0;
    byte_t sample = 0;
    byte_t channels = destBitCount >> 3;
    byte_t c = 0;
    long viewRight = viewLeft + viewWidth;
    long viewBottom = viewTop + viewHeight - 1;

    //uint_t t1 = GetTickCount() - t;

    int* x1a = new int[ viewWidth ];
    int* x1ca = new int[ viewWidth ];
    for ( long i = 0; i < viewWidth; i++ )
    {
        rx = ( i + viewLeft ) * ratio1;
        ix = ( int ) rx;
        x1 = ix > ( int ) p.width ? ( int ) width1 : ix;
        x1a[ i ] = x1;
        x1ca[ i ] = channels == 1 ? x1 : x1 * channels;
    }

    int* y1a = new int[ viewHeight ];
    int* y1sa = new int[ viewHeight ];
    for ( long j = 0; j < viewHeight; j++ )
    {
        ry = ( j + viewTop ) * ratio1;
        iy = ( int ) ry;
        y1 = iy > ( int ) p.height ? ( int ) height1 : iy;
        y1a[ j ] = y1;
        y1sa[ j ] = y1 * p.rowStride;
    }

    //uint_t t2 = GetTickCount() - t;

    for ( long j = 0; j < viewHeight; j++ )
    {
        int y1s = y1sa[ j ];
        byte_t* lbase = p.pixels + y1s;
        byte_t* pline = line;

        for ( long i = 0; i < viewWidth; i++ )
        {
            byte_t* sbc = lbase + x1ca[ i ];

            if ( bw )
            {
                *pline = ::getBit( lbase, x1a[ i ], p.paletteInverted ) ? 0x00 : 0xFF;
            }
            else
            {
                ::memcpy( pline, sbc, channels );
            }
            pline += channels;
        }

        line += destStride;
    }

    // cleanup
    delete[] x1a;
    delete[] x1ca;
    delete[] y1a;
    delete[] y1sa;

    *dest = ( BITMAPINFOHEADER* ) destData;

    //t = GetTickCount() - t;
    //::printf( "resizeImageNearest: %i ms\n", t );

    return true;
}

size_t resizeImageNearest( byte_t** dest, long viewLeft, long viewTop, long viewWidth, long viewHeight, double zoomFactor, const Pixels* pixels )
{
    const Pixels& p = *pixels;
    *dest = NULL;
    byte_t* destData = NULL;

    long width1 = p.width - 1;
    long height1 = p.height - 1;

    uint16_t destBitCount = p.bitsPerPixel == 24 ? 24 : 8;
    uint_t destStride = ( ( viewWidth * destBitCount + 7 ) & ( ~7 ) ) >> 3;
    bool bw = p.bitsPerPixel == 1;
    uint_t destLength = destStride * viewHeight;
    destData = ( byte_t* ) ::malloc( destLength );

    double ratio1 = 1 / zoomFactor;

    byte_t* line = destData;

    double rx, ry;
    int ix, iy;
    int x1, y1;
    double b1 = 0;
    byte_t sample = 0;
    byte_t channels = destBitCount >> 3;
    byte_t c = 0;
    long viewRight = viewLeft + viewWidth;
    long viewBottom = viewTop + viewHeight - 1;

    int* x1a = new int[ viewWidth ];
    int* x1ca = new int[ viewWidth ];
    for ( long i = 0; i < viewWidth; i++ )
    {
        rx = ( i + viewLeft ) * ratio1;
        ix = ( int ) rx;
        x1 = ix > ( int ) p.width ? ( int ) width1 : ix;
        x1a[ i ] = x1;
        x1ca[ i ] = channels == 1 ? x1 : x1 * channels;
    }

    int* y1a = new int[ viewHeight ];
    int* y1sa = new int[ viewHeight ];
    for ( long j = 0; j < viewHeight; j++ )
    {
        ry = ( j + viewTop ) * ratio1;
        iy = ( int ) ry;
        y1 = iy > ( int ) p.height ? ( int ) height1 : iy;
        y1a[ j ] = y1;
        y1sa[ j ] = y1 * p.rowStride;
    }

    for ( long j = 0; j < viewHeight; j++ )
    {
        int y1s = y1sa[ j ];
        byte_t* lbase = p.pixels + y1s;
        byte_t* pline = line;

        for ( long i = 0; i < viewWidth; i++ )
        {
            byte_t* sbc = lbase + x1ca[ i ];

            if ( bw )
            {
                *pline = ::getBit( lbase, x1a[ i ], p.paletteInverted ) ? 0x00 : 0xFF;
            }
            else
            {
                ::memcpy( pline, sbc, channels );
            }
            pline += channels;
        }

        line += destStride;
    }

    // cleanup
    delete[] x1a;
    delete[] x1ca;
    delete[] y1a;
    delete[] y1sa;

    *dest = destData;

    return destLength;
}
